/*****************************************************************************/
/*                                                                           */
/*    Domino Operation System Video Module                                   */
/*                                                                           */
/*    Copyright (C) 2014 Laszlo Arvai                                        */
/*    All rights reserved.                                                   */
/*                                                                           */
/*    This software may be modified and distributed under the terms          */
/*    of the BSD license.  See the LICENSE file for details.                 */
/*                                                                           */
/*    ------------------------------------------------------------------     */
/*    PIC32 Black&White Video Signal Generator                               */
/*****************************************************************************/
/*                                                                           */
/* Hardware used for video signal generator                                  */
/*                 330                                                       */
/* OC3  (sync)  --\/\/\/--                                                   */
/*                 180    |                                                  */
/* SDO1 (video) --\/\/\/-- -------o Video Out                                */
/*                        |                                                  */
/*                        \                                                  */
/*                        /                                                  */
/*                    120 \                                                  */
/*                        /                                                  */
/*                        |                                                  */
/*                        _                                                  */
/*                                                                           */
/* OC1 (screen) --                                                           */
/*                |                                                          */
/* SS1 -----------                                                           */
/*                                                                           */
/*****************************************************************************/

///////////////////////////////////////////////////////////////////////////////
// Incudes
#include <krnlTypes.h>
#include "drvIOConfig.h"

//#define videoNTSC

#define ROUNDDIV(dividend,divisor) ((dividend+(divisor/2))/divisor)

#define videoHORIZONTAL_RESOLUTION 256
#define videoVERTICAL_RESOLUTION 192
#define videoBORDER_WIDTH_IN_PIXEL 25

#define videoTIMER_FREQUENCY sysPBLCK_FREQUENCY // input clock frequency of the video timer
#define videoCLOCK_FREQUENCY sysPBLCK_FREQUENCY // input clock frequency of the video pixel clock generator

///////////////////////////////////////////////////////////////////////////////
// Constants

// Video timing constants
#ifdef videoNTSC

// Timing contants for NTSC video generation
#define videoTOTAL_NUMBER_OF_LINES 262  // number of lines in NTSC frame
#define videoHORIZONTAL_SYNC_FREQ 15734	// Tpb clock in a line (63.5us)

#define videoBACK_PORCH_WIDTH		4700	// minimum length of the back-porch signal in ns
#define videoFRONT_PORCH_WIDTH	1500	// minimum length og the pront-porch in ns
#define videoSYNC_WIDTH				4700	// sync signal width in ns
#else
// Timing constants for PAL video generation
#define videoTOTAL_NUMBER_OF_LINES 312	// number of lines in PAL frame
#define videoHORIZONTAL_SYNC_FREQ 15625 // horizontal sync pulse frequency in PAL system

#define videoBACK_PORCH_WIDTH		5700	// minimum length of the back-porch signal in ns
#define videoFRONT_PORCH_WIDTH	1650	// minimum length og the pront-porch in ns
#define videoSYNC_WIDTH					4700	// sync signal width in ns

#endif

// count the number of remaining black lines top+bottom
#define videoVSYNC_LINE_COUNT		3          // V sync lines
#define videoBLANK_LINE_COUNT		(videoTOTAL_NUMBER_OF_LINES-videoVERTICAL_RESOLUTION-videoVSYNC_LINE_COUNT)
#define videoPREEQ_LINE_COUNT		(videoBLANK_LINE_COUNT/2)         // preeq + bottom blank
#define videoPOSTEQ_LINE_COUNT	(videoBLANK_LINE_COUNT-videoPREEQ_LINE_COUNT)  // posteq + top blank

// Video State values
#define VS_PREEQ    0
#define VS_SYNC     1
#define VS_POSTEQ   2
#define VS_LINE     3

// range conversion constants
#define videoNANOSEC_DIVISOR 1000000000
#define videoMICROSEC_DIVISOR 1000000
#define videoNANOSEC_TO_MICROSEC_DIVISOR 1000

// useful video line with in ns
#define videoDISPLAYED_MAX_LINE_WIDTH ((videoNANOSEC_DIVISOR / videoHORIZONTAL_SYNC_FREQ) - videoBACK_PORCH_WIDTH - videoFRONT_PORCH_WIDTH - videoSYNC_WIDTH)
// Pixel clock
#define videoPIXEL_CLOCK_MIN (videoNANOSEC_DIVISOR / videoDISPLAYED_MAX_LINE_WIDTH * (videoHORIZONTAL_RESOLUTION + 2 * videoBORDER_WIDTH_IN_PIXEL))
// Pixel Clock BRG
#define videoPIXEL_CLOCK_BRG (videoCLOCK_FREQUENCY / (2 * videoPIXEL_CLOCK_MIN) - 1)
// Pixel Clock in Hz
#define videoPIXEL_CLOCK (videoCLOCK_FREQUENCY / (2 * (videoPIXEL_CLOCK_BRG+1)))
// Displayed effective pixel area
#define videoDISPLAYED_LINE_WIDTH (videoNANOSEC_DIVISOR/videoPIXEL_CLOCK * (videoHORIZONTAL_RESOLUTION + 2 * videoBORDER_WIDTH_IN_PIXEL))

// divisor for horizontal sync generator
#define videoHORIZONTAL_SYNC_DIVISOR (videoPIXEL_CLOCK / videoHORIZONTAL_SYNC_FREQ * (videoPIXEL_CLOCK_BRG+1) * 2 )
// timing for composite video horizontal state machine
#define videoSYNC_CYCLE (videoTIMER_FREQUENCY / videoNANOSEC_TO_MICROSEC_DIVISOR * videoSYNC_WIDTH / videoMICROSEC_DIVISOR) // calculation is done in mucrosec to avoid overflow
// timing cycle for back-porch
#define videoBACK_PORCH_CYCLE ROUNDDIV(videoTIMER_FREQUENCY / 1000 * videoBACK_PORCH_WIDTH, videoMICROSEC_DIVISOR)

// pixel (display) start position in ns (from the falling edge of the sync)
#define videoPIXEL_START ((videoDISPLAYED_MAX_LINE_WIDTH - videoDISPLAYED_LINE_WIDTH)/2 + videoSYNC_WIDTH + videoBACK_PORCH_WIDTH)
// pixel start position in eight pixel units
#define videoPIXEL_START_BYTES (videoPIXEL_CLOCK / videoNANOSEC_TO_MICROSEC_DIVISOR * videoPIXEL_START / videoMICROSEC_DIVISOR / 8)

// pixel stop position in ns
//#define videoPIXEL_STOP (videoPIXEL_START + ((videoHORIZONTAL_RESOLUTION + 10)* videoPIXEL_CLOCK_DIVISOR * 1000 / (videoCLOCK_FREQUENCY / videoMICROSEC_DIVISOR)))

// pixel start position in video timer cycle
//#define videoPIXEL_START_CYCLE (ROUNDDIV(videoTIMER_FREQUENCY / videoMICROSEC_DIVISOR * videoPIXEL_START, 1000))
// pixel stop position in timer cycle
//#define videoPIXEL_STOP_CYCLE (ROUNDDIV(videoTIMER_FREQUENCY / videoMICROSEC_DIVISOR * videoPIXEL_STOP, 1000)-10)



///////////////////////////////////////////////////////////////////////////////
// Configuration check
#if videoPIXEL_CLOCK < videoPIXEL_CLOCK_MIN
#error "Pixel Clock can't generated using the current settings. Try to change the horizontal resolution, border width or Fpb clock"
#endif

static dosByte l_zero[videoPIXEL_START_BYTES]= {0};

///////////////////////////////////////////////////////////////////////////////
// Global variables
//dosByte g_video_bitmap[ videoVERTICAL_RESOLUTION*(videoHORIZONTAL_RESOLUTION/8)]; // image buffer
dosByte* g_video_frame_buffer;

///////////////////////////////////////////////////////////////////////////////
// Module global variables
static dosWord l_vertical_line_count;
static dosByte l_video_state;
static dosBool l_callback_is_running;
volatile dosByte* l_video_address;


void line( short x0, short y0, short x1, short y1);


void drvVideoInit( void)
{
	int x,y;

	// clear video memory
	for( y=0; y<videoVERTICAL_RESOLUTION; y++)
		for (x=0; x<videoHORIZONTAL_RESOLUTION/8; x++)
			g_video_frame_buffer[y*videoHORIZONTAL_RESOLUTION/8 + x]= 0;

	//line(0,0,videoHORIZONTAL_RESOLUTION-1,0);
	//line(videoHORIZONTAL_RESOLUTION-1,0,videoHORIZONTAL_RESOLUTION-1,videoVERTICAL_RESOLUTION-1);
	//line(videoHORIZONTAL_RESOLUTION-1,videoVERTICAL_RESOLUTION-1,0,videoVERTICAL_RESOLUTION-1);
	//line(0,videoVERTICAL_RESOLUTION-1,0,0);
	//line(0,0,videoHORIZONTAL_RESOLUTION-1,videoVERTICAL_RESOLUTION-1);
	//line(videoHORIZONTAL_RESOLUTION-1,0,0,videoVERTICAL_RESOLUTION-1);

  // Init vertical sync state machine
  l_video_state = VS_LINE;
  l_vertical_line_count = 1;

	// Init the SPI1 for pixel shifting
	SpiChnOpen( 1, SPICON_ON | SPICON_MSTEN | SPICON_FRMEN | SPICON_FRMSYNC | SPICON_FRMPOL, (videoPIXEL_CLOCK_BRG + 1) * 2 );

	 // DMA configuration for back porch extension
  DmaChnOpen( 1, 1, DMA_OPEN_DEFAULT);
  DmaChnSetEventControl( 1, DMA_EV_START_IRQ_EN | DMA_EV_START_IRQ(_SPI1_TX_IRQ));
  DmaChnSetTxfer( 1, (void*)l_zero, (void *)&SPI1BUF, videoPIXEL_START_BYTES, 1, 1);

	// Init DMA0 for video data transfer
	DmaChnOpen( 0, DMA_CHN_PRI0, DMA_OPEN_DEFAULT);
	DmaChnSetEventControl( 0, DMA_EV_START_IRQ_EN | DMA_EV_START_IRQ(_SPI1_TX_IRQ));
	DmaChnSetTxfer( 0, (void*)g_video_frame_buffer, (void*)&SPI1BUF, videoHORIZONTAL_RESOLUTION/8+1, 1, 1);

  // chain DMA0 to completion of DMA1 transfer
  DmaChnSetControl( 0, DMA_CTL_CHAIN_EN | DMA_CTL_CHAIN_DIR);

	// Open output compare for sync generation
	OpenOC3( OC_ON | OC_TIMER3_SRC | OC_CONTINUE_PULSE, 0, videoSYNC_CYCLE);

	// Initialize timer for video signal generation
	OpenTimer3(T3_ON | T3_PS_1_1 | T3_SOURCE_INT, videoHORIZONTAL_SYNC_DIVISOR-1);
	ConfigIntOC3(OC_INT_OFF | OC_INT_PRIOR_1 | OC_INT_SUB_PRI_0); // Enable interrupt for callback calling
	ConfigIntTimer3(T3_INT_ON | T3_INT_PRIOR_7 | T3_INT_SUB_PRIOR_0);

} // initVideo

void __ISR(_TIMER_3_VECTOR, ipl7srs) T3Interrupt( void)
{
	_RA3 = 1;

	// switch to next video state
	if(--l_vertical_line_count == 0)
	{
		switch(l_video_state)
		{
			case VS_PREEQ:  // 0
				// next video state
				l_vertical_line_count = videoVSYNC_LINE_COUNT;
				l_video_state = VS_SYNC;

				// vertical sync pulse
				OC3R = videoHORIZONTAL_SYNC_DIVISOR - videoSYNC_CYCLE - videoBACK_PORCH_CYCLE;
				break;

			case VS_SYNC:   // 1
				l_vertical_line_count = videoPOSTEQ_LINE_COUNT;
				l_video_state = VS_POSTEQ;

				// horizontal sync pulse
				OC3R = videoSYNC_CYCLE;
				break;

			case VS_POSTEQ: // 2
				l_vertical_line_count = videoVERTICAL_RESOLUTION;
				l_video_address = g_video_frame_buffer;
				l_video_state = VS_LINE;
				break;

			case VS_LINE:   // 3
			default:
				l_video_state = VS_PREEQ;
				l_vertical_line_count = videoPREEQ_LINE_COUNT;
				// enable OC3 interrupt for callback calling
				mOC3IntEnable(1);
				break;
		}
	}

	// handle video state
	if (l_video_state == VS_LINE)
	{
		DCH0SSA = KVA_TO_PA((void*) l_video_address-1);
		l_video_address += videoHORIZONTAL_RESOLUTION/8;
		DmaChnEnable( 1);
	}

	// clear the interrupt flag
	mT3ClearIntFlag();
	_RA3 = 0;
}

void __ISR(_OUTPUT_COMPARE_3_VECTOR, ipl1soft) OC3Interrupt( void)
{
	mOC3IntEnable(0);

	// call vblank callback
	if(!l_callback_is_running)
	{
		l_callback_is_running = dosTrue;
		_RA2 = 1;
		videoVBlankCallback();
		_RA2 = 0;
		l_callback_is_running = dosFalse;
	}

	mOC3ClearIntFlag();
}

#if 0
///////////////////////////////////////////////////////////////////////////////
// Module global variables
static dosWord l_vertical_line_count;
static dosByte l_video_state;
static dosBool l_callback_is_running;
volatile dosByte* l_video_address;

///////////////////////////////////////////////////////////////////////////////
// Global variables
//dosByte g_video_bitmap[ videoVERTICAL_RESOLUTION*(videoHORIZONTAL_RESOLUTION/8)]; // image buffer
dosByte* g_video_frame_buffer;

static dosByte l_zero[64]= {0x00, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
														0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
														0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
														0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
														0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
														0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
														0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
														0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };

void line( short x0, short y0, short x1, short y1);


///////////////////////////////////////////////////////////////////////////////
// Initialize video generator
void drvVideoInit(void)
{
	int x, y;
	dosDWord value = videoPIXEL_CLOCK_DIVISOR;

	for( y=0; y<videoVERTICAL_RESOLUTION; y++)
		for (x=0; x<videoHORIZONTAL_RESOLUTION/8; x++)
			g_video_frame_buffer[y*videoHORIZONTAL_RESOLUTION/8 + x]= 0;

	//line(0,0,videoHORIZONTAL_RESOLUTION-1,0);
	//line(videoHORIZONTAL_RESOLUTION-1,0,videoHORIZONTAL_RESOLUTION-1,videoVERTICAL_RESOLUTION-1);
	//line(videoHORIZONTAL_RESOLUTION-1,videoVERTICAL_RESOLUTION-1,0,videoVERTICAL_RESOLUTION-1);
	line(0,videoVERTICAL_RESOLUTION-1,0,0);
	//line(0,0,videoHORIZONTAL_RESOLUTION-1,videoVERTICAL_RESOLUTION-1);
	//line(videoHORIZONTAL_RESOLUTION-1,0,0,videoVERTICAL_RESOLUTION-1);

	//g_video_frame_buffer[0*videoHORIZONTAL_RESOLUTION/8 + 0]= 0xff;

	/*
	for (x=0; x<videoHORIZONTAL_RESOLUTION/8; x++)
	{
		g_video_frame_buffer[0*videoHORIZONTAL_RESOLUTION/8 + x]= 0xff;
		g_video_frame_buffer[(videoVERTICAL_RESOLUTION-1)*videoHORIZONTAL_RESOLUTION/8 + x]= 0xff;
	}

	for( y=1; y<videoVERTICAL_RESOLUTION-1; y++)
	{
		g_video_frame_buffer[y*videoHORIZONTAL_RESOLUTION/8 + 0]= 0x80;
		//g_video_frame_buffer[y*videoHORIZONTAL_RESOLUTION/8 + 1]= 0x55;
//		g_video_frame_buffer[y*videoHORIZONTAL_RESOLUTION/8 + (videoHORIZONTAL_RESOLUTION/8-1)]= 0x01;
		//		g_video_frame_buffer[y*videoHORIZONTAL_RESOLUTION/8 + (videoHORIZONTAL_RESOLUTION/8-1)]= 0x01;
		g_video_frame_buffer[y*videoHORIZONTAL_RESOLUTION/8 + (videoHORIZONTAL_RESOLUTION/8-1)]= 0x01;
	}

	g_video_frame_buffer[0] = 0xaa;
	g_video_frame_buffer[videoHORIZONTAL_RESOLUTION/8-1] = 0x55;
*/
	// General initialization
	l_callback_is_running = dosFalse;

  // Init vertical sync state machine
  l_video_state = VS_LINE;
  l_vertical_line_count = 1;

  // Init the SPI1 for pixel shifting
  SpiChnOpen( 1, SPICON_ON | SPICON_MSTEN, videoPIXEL_CLOCK_DIVISOR);

	// Init DMA0 for video data transfer
	DmaChnOpen( 0, DMA_CHN_PRI0, DMA_OPEN_DEFAULT);
	DmaChnSetEventControl( 0, DMA_EV_START_IRQ_EN | DMA_EV_START_IRQ(_SPI1_TX_IRQ));
	DmaChnSetTxfer( 0, (void*)g_video_frame_buffer, (void*)&SPI1BUF, videoHORIZONTAL_RESOLUTION/8, 1, 1);

	// Back porch and border generation

	// Open output compare for back porch generation
	OpenOC2( OC_ON | OC_TIMER3_SRC | OC_CONTINUE_PULSE, videoPIXEL_START_CYCLE, videoPIXEL_STOP_CYCLE );

  // DMA for back porch extension
  DmaChnOpen( 1, DMA_CHN_PRI1, DMA_OPEN_DEFAULT);
  DmaChnSetEventControl( 1, DMA_EV_START_IRQ_EN | DMA_EV_START_IRQ(_OUTPUT_COMPARE_2_IRQ));
  DmaChnSetTxfer( 1, (void*)l_zero, (void *)&SPI1BUF, 1, 1, 1);
  DmaChnSetControl( 0, DMA_CTL_CHAIN_EN | DMA_CTL_CHAIN_DIR); // chain DMA0 to completion of DMA1 transfer

	// Open output compare for sync generation
	OpenOC3( OC_ON | OC_TIMER3_SRC | OC_CONTINUE_PULSE, 0, videoSYNC_CYCLE);

	// Initialize timer for video signal generation
	OpenTimer3(T3_ON | T3_PS_1_1 | T3_SOURCE_INT, 5127/*videoHORIZONTAL_SYNC_DIVISOR*/);
	//ConfigIntOC3(OC_INT_OFF | OC_INT_PRIOR_1 | OC_INT_SUB_PRI_0); // Enable interrupt
	ConfigIntTimer3(T3_INT_ON | T3_INT_PRIOR_7 | T3_INT_SUB_PRIOR_0);
}

void __ISR(_OUTPUT_COMPARE_3_VECTOR, ipl1soft) OC3Interrupt( void)
{
	mOC3IntEnable(0);

	// call vblank callback
	if(!l_callback_is_running)
	{
		l_callback_is_running = dosTrue;
		_RA2 = 1;
		//videoVBlankCallback();
		_RA2 = 0;
		l_callback_is_running = dosFalse;
	}

	mOC3ClearIntFlag();
}

void __ISR(_TIMER_3_VECTOR, ipl7srs) T3Interrupt( void)
{
	_RA3 = 1;

	// switch to next video state
	if(--l_vertical_line_count == 0)
	{
		switch(l_video_state)
		{
			case VS_PREEQ:  // 0
				// next video state
				l_vertical_line_count = videoVSYNC_LINE_COUNT;
				l_video_state = VS_SYNC;

				// vertical sync pulse
				OC3R = videoHORIZONTAL_SYNC_DIVISOR - videoSYNC_CYCLE - videoBACK_PORCH_CYCLE;
				break;

			case VS_SYNC:   // 1
				l_vertical_line_count = videoPOSTEQ_LINE_COUNT;
				l_video_state = VS_POSTEQ;

				// horizontal sync pulse
				OC3R = videoSYNC_CYCLE;
				break;

			case VS_POSTEQ: // 2
				l_vertical_line_count = videoVERTICAL_RESOLUTION;
				l_video_address = g_video_frame_buffer;
				l_video_state = VS_LINE;
				break;

			case VS_LINE:   // 3
			default:
				l_video_state = VS_PREEQ;
				l_vertical_line_count = videoPREEQ_LINE_COUNT;
				// enable OC3 interrupt for callback calling
				mOC3IntEnable(1);
				break;
		}
	}

	// handle video state
	if (l_video_state == VS_LINE)
	{
		DCH0SSA = KVA_TO_PA((void*)(l_video_address));
		DCH1SSA = KVA_TO_PA((void*)(l_video_address));
		l_video_address += videoHORIZONTAL_RESOLUTION / 8;
		DmaChnEnable(1);
	}

	// clear the interrupt flag
	mT3ClearIntFlag();
	_RA3 = 0;

}
#endif

void plot( unsigned x, unsigned y)
{
    if ((x<videoHORIZONTAL_RESOLUTION) && (y<videoVERTICAL_RESOLUTION) )
        g_video_frame_buffer[ ((videoVERTICAL_RESOLUTION-1-y)*(videoHORIZONTAL_RESOLUTION/8)+(x>>3))] |= ( 0x80>>(x&0x7));
} // plot

#define abs( a)     (((a)> 0) ? (a) : -(a))

void line( short x0, short y0, short x1, short y1)
{
     short steep, t ;
     short deltax, deltay, error;
     short x, y;
     short ystep;

    // simple clipping
     if (( x0 < 0) || (x0>videoHORIZONTAL_RESOLUTION))
        return;
     if (( x1 < 0) || (x1>videoHORIZONTAL_RESOLUTION))
        return;
     if (( y0 < 0) || (y0>videoVERTICAL_RESOLUTION))
        return;
     if (( y1 < 0) || (y1>videoVERTICAL_RESOLUTION))
        return;

     steep = ( abs(y1 - y0) > abs(x1 - x0));

     if ( steep )
     { // swap x and y
         t = x0; x0 = y0; y0 = t;
         t = x1; x1 = y1; y1 = t;
     }
     if (x0 > x1)
     {  // swap ends
         t = x0; x0 = x1; x1 = t;
         t = y0; y0 = y1; y1 = t;
     }

     deltax = x1 - x0;
     deltay = abs(y1 - y0);
     error = 0;
     y = y0;

     if (y0 < y1) ystep = 1; else ystep = -1;
     for (x = x0; x < x1; x++)
     {
         if ( steep) plot(y,x); else plot(x,y);
         error += deltay;
         if ( (error<<1) >= deltax)
         {
             y += ystep;
             error -= deltax;
         } // if
     } // for
} // line

#if 0
/*
** graphic.c
**
** Composite Video using:
**      T3      time based
**      OC3     Horizontal Synchronization pulse
**      DMA0    image data
**      DMA1    back porch extension
**      SPI1 in Frame Slave Mode
**
** Double buffered Graphic page for PIC32MX
**  8/02/07 v2.2
**  10/23/07 v3.0 72MHz operation
**  11/20/07 v3.1 removed OC3 interrupt
*/

#include <p32xxxx.h>
#include <plib.h>
#include <string.h>

#define  HRES 256
#define VRES 192
#define FPB_FREQ sysPBLCK_FREQUENCY

// timing for composite video vertical state machine
#ifdef NTSC
#define LINE_N   262        // number of lines in NTSC frame
#define LINE_T   (FPB_FREQ/15734) // Tpb clock in a line (63.5us)
#else
#define LINE_N   312        // number of lines in PAL frame
#define LINE_T   (FPB_FREQ/15625)       // Tpb clock in a line (64us)
#endif

// count the number of remaining black lines top+bottom
#define VSYNC_N  3          // V sync lines
#define VBLANK_N (LINE_N-VRES-VSYNC_N)
#define PREEQ_N  (VBLANK_N/2)         // preeq + bottom blank
#define POSTEQ_N (VBLANK_N-PREEQ_N)  // posteq + top blank

// definition of the vertical sync state machine
#define SV_PREEQ    0
#define SV_SYNC     1
#define SV_POSTEQ   2
#define SV_LINE     3

// timing for composite video horizontal state machine
#define PIX_T    11          // Tpb clock per pixel
#define HSYNC_T  (180/2)        // Tpb clock width horizontal pulse
#define BPORCH_T (340/2)        // Tpb clock width back porch

int VMap1[ VRES*(HRES/32)]; // image buffer
int *VA = VMap1;            // pointer to the Active VMap
int *VH = VMap1;            // pointer to the Hidden VMap

#ifdef DOUBLE_BUFFER
int VMap2[ VRES*(HRES/32)]; // second image buffer
#endif

volatile int *VPtr;
volatile short VCount;
volatile short VState;

// next state table
short VS[4] = { SV_SYNC, SV_POSTEQ, SV_LINE, SV_PREEQ};
// next counter table
short int VC[4] = { VSYNC_N,  POSTEQ_N,    VRES,  PREEQ_N};

int zero[4]= {0x0, 0x0, 0x0, 0x0};


void __ISR(_TIMER_3_VECTOR, ipl7) T3Interrupt( void)
{
_RA2=1;
    // advance the state machine
    if ( --VCount == 0)
    {
        VCount = VC[ VState&3];
        VState = VS[ VState&3];
    }

    // vertical state machine
    switch ( VState) {
        case SV_SYNC:   // 1
            // vertical sync pulse
            OC3R = LINE_T - HSYNC_T - BPORCH_T;
            break;

        case SV_POSTEQ: // 2
          // horizontal sync pulse
            OC3R = HSYNC_T;
            break;

        case SV_PREEQ:  // 0
            // prepare for the new frame
            VPtr = VA;
            break;

        default:
        case SV_LINE:   // 3
            // preload of the SPI waiting for SS (Synch high)
            SPI1BUF = 0;
            // update the DMA0 source address and enable it
            DCH0SSA = KVA_TO_PA((void*) VPtr);
            VPtr += HRES/32;
            DmaChnEnable( 1);
            break;
    } //switch

    // clear the interrupt flag
    mT3ClearIntFlag();
_RA2=0;
} // T3Interrupt

void line( short x0, short y0, short x1, short y1);


void drvVideoInit( void)
{
	int x,y;
/*
 * 	for (x=0; x<HRES/32; x++)
	{
		VMap1[0*HRES/32 + x]= 0xffffffff;
		VMap1[(VRES-1)*HRES/32 + x]= 0xffffffff;
	}
	VMap1[0*HRES/32 + 0]= 0x55555555;
	VMap1[(VRES-1)*HRES/32 + 0]= 0x55555555;

	VMap1[0*HRES/32 + ((HRES-1)/32)]= 0x55555555;
	VMap1[(VRES-1)*HRES/32 + ((HRES-1)/32)]= 0x55555555;

	for( y=1; y<VRES-1; y++)
	{
		//VMap1[y*HRES/32 + 0]= 0xffffffff;
		VMap1[y*HRES/32 + 0]= 0x80000000;
		VMap1[y*HRES/32 + (HRES/32-1)]= 0x01;
	}

	VMap1[0]= 0xffffffff;
	VMap1[(HRES-1)/32]= 0xffffffff;
	VMap1[(VRES-1)*HRES/32 + 0]= 0xffffffff;
	VMap1[(VRES-1)*HRES/32 + (HRES-1)/32]= 0xffffffff;


	VMap1[80*HRES/32 + 0]= 0xffffffff;
	VMap1[80*HRES/32 + 1]= 0x00000000;
	VMap1[80*HRES/32 + 2]= 0xffffffff;
	VMap1[80*HRES/32 + 3]= 0x00000000;
	VMap1[80*HRES/32 + 4]= 0xffffffff;
	VMap1[80*HRES/32 + 5]= 0x00000000;
	VMap1[80*HRES/32 + 6]= 0xffffffff;
	VMap1[80*HRES/32 + 7]= 0x00000000;

	VMap1[80*HRES/32 + 8]= 0xffffffff;
	VMap1[80*HRES/32 + 9]= 0x00000000;
	VMap1[80*HRES/32 + 10]= 0xffffffff;
	VMap1[80*HRES/32 + 11]= 0x00000000;
	VMap1[80*HRES/32 + 12]= 0xffffffff;
	VMap1[80*HRES/32 + 13]= 0x00000000;
	VMap1[80*HRES/32 + 14]= 0xffffffff;
	VMap1[80*HRES/32 + 15]= 0x00000000;
*/

	line(0,0,HRES-1,0);
	line(HRES-1,0,HRES-1,VRES-1);
	line(HRES-1,VRES-1,0,VRES-1);
	line(0,VRES-1,0,0);
	line(0,0,HRES-1,VRES-1);
	line(HRES-1,0,0,VRES-1);
	// 1. init the SPI1
    // select framed slave mode to synch SPI with OC3
    SpiChnOpen( 1, SPICON_ON | SPICON_MSTEN | SPICON_MODE32
              | SPICON_FRMEN | SPICON_FRMSYNC | SPICON_FRMPOL
              , PIX_T);

    // 2. make SS1(RB2) a digital input
    //AD1PCFGSET = 0x0004;

    // 3. init OC3 in single pulse, continuous mode
    OpenOC3( OC_ON | OC_TIMER3_SRC | OC_CONTINUE_PULSE,
                0, HSYNC_T);

    // 4. Timer3 on, prescaler 1:1, internal clock, period
    OpenTimer3(  T3_ON | T3_PS_1_1 | T3_SOURCE_INT, LINE_T-1);

    // 5. init the vertical sync state machine
    VState = SV_LINE;
    VCount = 1;

    // 6. init the active and hidden screens pointers
    VA = VMap1;
#ifdef DOUBLE_BUFFER
    VH = VMap2;
#else
    VH = VA;
#endif
    // 7. DMA 1 configuration   back porch extension
    DmaChnOpen( 1, 1, DMA_OPEN_DEFAULT);
    DmaChnSetEventControl( 1, DMA_EV_START_IRQ_EN |
                          DMA_EV_START_IRQ(_SPI1_TX_IRQ));
    DmaChnSetTxfer( 1, (void*)zero, (void *)&SPI1BUF, 15, 4, 4);

    // 8. DMA 0 configuration  image serialization
    DmaChnOpen( 0, 0, DMA_OPEN_DEFAULT);
    DmaChnSetEventControl( 0, DMA_EV_START_IRQ_EN |
                            DMA_EV_START_IRQ(_SPI1_TX_IRQ));
    DmaChnSetTxfer( 0, (void*)VPtr, (void *)&SPI1BUF,
                    HRES/8, 4, 4);
    // chain DMA0 to completion of DMA1 transfer
    DmaChnSetControl( 0, DMA_CTL_CHAIN_EN | DMA_CTL_CHAIN_DIR);

		// 9. Enable Timer3 Interrupts
    // set the priority level 7 to use shadow register set
    mT3SetIntPriority( 7);
    mT3IntEnable( 1);

} // initVideo

void plot( unsigned x, unsigned y)
{
    if ((x<HRES) && (y<VRES) )
        VH[ ((VRES-1-y)<<3)+(x>>5)] |= ( 0x80000000>>(x&0x1f));
} // plot

#define abs( a)     (((a)> 0) ? (a) : -(a))

void line( short x0, short y0, short x1, short y1)
{
     short steep, t ;
     short deltax, deltay, error;
     short x, y;
     short ystep;

    // simple clipping
     if (( x0 < 0) || (x0>HRES))
        return;
     if (( x1 < 0) || (x1>HRES))
        return;
     if (( y0 < 0) || (y0>VRES))
        return;
     if (( y1 < 0) || (y1>HRES))
        return;

     steep = ( abs(y1 - y0) > abs(x1 - x0));

     if ( steep )
     { // swap x and y
         t = x0; x0 = y0; y0 = t;
         t = x1; x1 = y1; y1 = t;
     }
     if (x0 > x1)
     {  // swap ends
         t = x0; x0 = x1; x1 = t;
         t = y0; y0 = y1; y1 = t;
     }

     deltax = x1 - x0;
     deltay = abs(y1 - y0);
     error = 0;
     y = y0;

     if (y0 < y1) ystep = 1; else ystep = -1;
     for (x = x0; x < x1; x++)
     {
         if ( steep) plot(y,x); else plot(x,y);
         error += deltay;
         if ( (error<<1) >= deltax)
         {
             y += ystep;
             error -= deltax;
         } // if
     } // for
} // line
#endif

#if 0
//#define videoNTSC

#define videoBACK_PORCH_BY_DMA 1
//#define videoBACK_PORCH_BY_TIMER 1

#define ROUNDDIV(dividend,divisor) ((dividend+(divisor/2))/divisor)

#define videoHORIZONTAL_RESOLUTION 256
#define videoVERTICAL_RESOLUTION 192
#define videoBORDER_WIDTH_IN_PIXEL 40

#define videoTIMER_FREQUENCY 80000000 // input clock frequency of the video timer
#define videoCLOCK_FREQUENCY 80000000 // input clock frequency of the video pixel clock generator

///////////////////////////////////////////////////////////////////////////////
// Constants

// Video timing constants
#ifdef videoNTSC

// Timing contants for NTSC video generation
#define videoTOTAL_NUMBER_OF_LINES 262  // number of lines in NTSC frame
#define videoHORIZONTAL_SYNC_FREQ 15734	// Tpb clock in a line (63.5us)

#define videoBACK_PORCH_WIDTH		4700	// minimum length of the back-porch signal in ns
#define videoFRONT_PORCH_WIDTH	1500	// minimum length og the pront-porch in ns
#define videoSYNC_WIDTH				4700	// sync signal width in ns
#else
// Timing constants for PAL video generation
#define videoTOTAL_NUMBER_OF_LINES 312	// number of lines in PAL frame
#define videoHORIZONTAL_SYNC_FREQ 15625 // horizontal sync pulse frequency in PAL system

//#define videoBACK_PORCH_WIDTH		5700	// minimum length of the back-porch signal in ns
#define videoBACK_PORCH_WIDTH		6000	// minimum length of the back-porch signal in ns (it should be 5700 pes spec but this value gives more centered image)
#define videoFRONT_PORCH_WIDTH	1650	// minimum length og the pront-porch in ns
#define videoSYNC_WIDTH					4700	// sync signal width in ns

#endif

// count the number of remaining black lines top+bottom
#define videoVSYNC_LINE_COUNT		3          // V sync lines
#define videoBLANK_LINE_COUNT		(videoTOTAL_NUMBER_OF_LINES-videoVERTICAL_RESOLUTION-videoVSYNC_LINE_COUNT)
#define videoPREEQ_LINE_COUNT		(videoBLANK_LINE_COUNT/2)         // preeq + bottom blank
#define videoPOSTEQ_LINE_COUNT	(videoBLANK_LINE_COUNT-videoPREEQ_LINE_COUNT)  // posteq + top blank

// Video State values
#define VS_PREEQ    0
#define VS_SYNC     1
#define VS_POSTEQ   2
#define VS_LINE     3

// divisor for horizontal sync generator
#define videoHORIZONTAL_SYNC_DIVISOR ROUNDDIV(videoTIMER_FREQUENCY, videoHORIZONTAL_SYNC_FREQ)

// timing for composite video horizontal state machine
//#define videoSYNC_CYCLE ROUNDDIV(videoTIMER_FREQUENCY / 1000 * videoSYNC_WIDTH, videoMICROSEC_DIVISOR)
#define videoSYNC_CYCLE (videoTIMER_FREQUENCY / 1000 * videoSYNC_WIDTH / videoMICROSEC_DIVISOR)

// timing cycle for back-porch
#define videoBACK_PORCH_CYCLE ROUNDDIV(videoTIMER_FREQUENCY / 1000 * videoBACK_PORCH_WIDTH, videoMICROSEC_DIVISOR)

#define videoNANOSEC_DIVISOR 1000000000
#define videoMICROSEC_DIVISOR 1000000

// useful video line with in ns
#define videoDISPLAYED_LINE_WIDTH ((videoNANOSEC_DIVISOR / videoHORIZONTAL_SYNC_FREQ) - videoBACK_PORCH_WIDTH - videoFRONT_PORCH_WIDTH - videoSYNC_WIDTH)
// Pixel clock in kHz
#define videoPIXEL_CLOCK_MAX ROUNDDIV((videoHORIZONTAL_RESOLUTION + 2 * videoBORDER_WIDTH_IN_PIXEL) * videoMICROSEC_DIVISOR, videoDISPLAYED_LINE_WIDTH)
// Pixel Clock divisor
#define videoPIXEL_CLOCK_DIVISOR (videoCLOCK_FREQUENCY / videoPIXEL_CLOCK_MAX / 1000)
// pixel start position in ns
#define videoPIXEL_START (((videoDISPLAYED_LINE_WIDTH - (videoHORIZONTAL_RESOLUTION * videoPIXEL_CLOCK_DIVISOR * 1000 / (videoCLOCK_FREQUENCY / videoMICROSEC_DIVISOR))) / 2) + videoBACK_PORCH_WIDTH + videoSYNC_WIDTH)
// pixel stop position in ns 
#define videoPIXEL_STOP (videoPIXEL_START + ((videoHORIZONTAL_RESOLUTION + 10)* videoPIXEL_CLOCK_DIVISOR * 1000 / (videoCLOCK_FREQUENCY / videoMICROSEC_DIVISOR)))

// pixel start position in video timer cycle
#define videoPIXEL_START_CYCLE (ROUNDDIV(videoTIMER_FREQUENCY / videoMICROSEC_DIVISOR * videoPIXEL_START, 1000))
// pixel stop position in timer cycle
#define videoPIXEL_STOP_CYCLE (ROUNDDIV(videoTIMER_FREQUENCY / videoMICROSEC_DIVISOR * videoPIXEL_STOP, 1000)-10)

///////////////////////////////////////////////////////////////////////////////
// Configuration check
#if videoPIXEL_CLOCK_DIVISOR == 0
#error "Pixel Clock Divisor can't be zero"
#endif

#if !defined(videoBACK_PORCH_BY_DMA) && !defined(videoBACK_PORCH_BY_TIMER)
#error "Back porch mode must be defined"
#endif

///////////////////////////////////////////////////////////////////////////////
// Module global variables
static dosWord l_vertical_line_count;
static dosByte l_video_state;
static dosBool l_callback_is_running;
volatile dosByte* l_video_address;

///////////////////////////////////////////////////////////////////////////////
// Global variables
//dosByte g_video_bitmap[ videoVERTICAL_RESOLUTION*(videoHORIZONTAL_RESOLUTION/8)]; // image buffer
dosByte* g_video_frame_buffer;

#ifdef videoBACK_PORCH_BY_DMA
static dosByte l_zero[64]= {0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
														0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
														0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
														0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
														0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
														0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
														0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0,
														0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 };
#endif

///////////////////////////////////////////////////////////////////////////////
// Initialize video generator
void drvVideoInit(void)
{
	int x, y;
	dosDWord value = videoPIXEL_CLOCK_DIVISOR;

	for( y=0; y<videoVERTICAL_RESOLUTION; y++)
		for (x=0; x<videoHORIZONTAL_RESOLUTION/8; x++)
			g_video_frame_buffer[y*videoHORIZONTAL_RESOLUTION/8 + x]= 0;
/*
	for (x=0; x<videoHORIZONTAL_RESOLUTION/8; x++)
	{
		g_video_frame_buffer[0*videoHORIZONTAL_RESOLUTION/8 + x]= 0xff;
		g_video_frame_buffer[(videoVERTICAL_RESOLUTION-1)*videoHORIZONTAL_RESOLUTION/8 + x]= 0xff;
	}

	for( y=1; y<videoVERTICAL_RESOLUTION-1; y++)
	{
		g_video_frame_buffer[y*videoHORIZONTAL_RESOLUTION/8 + 0]= 0x80;
		//g_video_frame_buffer[y*videoHORIZONTAL_RESOLUTION/8 + 1]= 0x55;
//		g_video_frame_buffer[y*videoHORIZONTAL_RESOLUTION/8 + (videoHORIZONTAL_RESOLUTION/8-1)]= 0x01;
		//		g_video_frame_buffer[y*videoHORIZONTAL_RESOLUTION/8 + (videoHORIZONTAL_RESOLUTION/8-1)]= 0x01;
		g_video_frame_buffer[y*videoHORIZONTAL_RESOLUTION/8 + (videoHORIZONTAL_RESOLUTION/8-1)]= 0x01;
	}
*/
	g_video_frame_buffer[0] = 0x01;
	// General initialization
	l_callback_is_running = dosFalse;

  // Init vertical sync state machine
  l_video_state = VS_LINE;
  l_vertical_line_count = 1;

  // Init the SPI1 for pixel shifting
  SpiChnOpen( 1, SPICON_ON | SPICON_MSTEN | SPICON_FRMEN | SPICON_FRMSYNC | SPICON_FRMPOL, videoPIXEL_CLOCK_DIVISOR);

	// Init DMA0 for video data transfer
	DmaChnOpen( 0, DMA_CHN_PRI0, DMA_OPEN_DEFAULT);
	DmaChnSetEventControl( 0, DMA_EV_START_IRQ_EN | DMA_EV_START_IRQ(_SPI1_TX_IRQ));
#ifdef videoBACK_PORCH_BY_TIMER
	DmaChnSetTxfer( 0, (void*)g_video_frame_buffer, (void*)&SPI1BUF, videoHORIZONTAL_RESOLUTION/8-1, 1, 1);
#endif
#ifdef videoBACK_PORCH_BY_DMA
	DmaChnSetTxfer( 0, (void*)g_video_frame_buffer, (void*)&SPI1BUF, videoHORIZONTAL_RESOLUTION/8, 1, 1);
#endif

	// Back porch and border generation
#ifdef videoBACK_PORCH_BY_TIMER
	// Open output compare for back porch generation
	OpenOC2( OC_ON | OC_TIMER3_SRC | /*OC_HIGH_LOW*/OC_CONTINUE_PULSE, videoPIXEL_START_CYCLE  , videoPIXEL_STOP_CYCLE);
#endif

#ifdef videoBACK_PORCH_BY_DMA
  // DMA for back porch extension
  DmaChnOpen( 1, DMA_CHN_PRI1, DMA_OPEN_DEFAULT);
  DmaChnSetEventControl( 1, DMA_EV_START_IRQ_EN | DMA_EV_START_IRQ(_SPI1_TX_IRQ));
  DmaChnSetTxfer( 1, (void*)l_zero, (void *)&SPI1BUF, 10, 1, 1);
  DmaChnSetControl( 0, DMA_CTL_CHAIN_EN | DMA_CTL_CHAIN_DIR); // chain DMA0 to completion of DMA1 transfer
#endif

	// Open output compare for sync generation
	OpenOC3( OC_ON | OC_TIMER3_SRC | OC_CONTINUE_PULSE, 0, videoSYNC_CYCLE);

	// Initialize timer for video signal generation
	OpenTimer3(T3_ON | T3_PS_1_1 | T3_SOURCE_INT, videoHORIZONTAL_SYNC_DIVISOR-1);
	//ConfigIntOC3(OC_INT_OFF | OC_INT_PRIOR_1 | OC_INT_SUB_PRI_0); // Enable interrupt
	ConfigIntTimer3(T3_INT_ON | T3_INT_PRIOR_7 | T3_INT_SUB_PRIOR_0);
}	

void __ISR(_OUTPUT_COMPARE_3_VECTOR, ipl1soft) OC3Interrupt( void)
{
	mOC3IntEnable(0);

	// call vblank callback
	if(!l_callback_is_running)
	{
		l_callback_is_running = dosTrue;
		_RA2 = 1;
		//videoVBlankCallback();
		_RA2 = 0;
		l_callback_is_running = dosFalse;
	}

	mOC3ClearIntFlag();
}

void __ISR(_TIMER_3_VECTOR, ipl7srs) T3Interrupt( void)
{
	_RA3 = 1;

	// switch to next video state
	if(--l_vertical_line_count == 0)
	{
		switch(l_video_state)
		{
			case VS_PREEQ:  // 0
				// next video state
				l_vertical_line_count = videoVSYNC_LINE_COUNT;
				l_video_state = VS_SYNC;

				// vertical sync pulse
				OC3R = videoHORIZONTAL_SYNC_DIVISOR - videoSYNC_CYCLE - videoBACK_PORCH_CYCLE;
				break;

			case VS_SYNC:   // 1
				l_vertical_line_count = videoPOSTEQ_LINE_COUNT;
				l_video_state = VS_POSTEQ;

				// horizontal sync pulse
				OC3R = videoSYNC_CYCLE;
				break;

			case VS_POSTEQ: // 2
				l_vertical_line_count = videoVERTICAL_RESOLUTION;
				l_video_address = g_video_frame_buffer;
				l_video_state = VS_LINE;
				break;

			case VS_LINE:   // 3
			default:
				l_video_state = VS_PREEQ;
				l_vertical_line_count = videoPREEQ_LINE_COUNT;
				// enable OC3 interrupt for callback calling
				mOC3IntEnable(1);
				break;
		}
	}

	// handle video state
	if (l_video_state == VS_LINE)
	{
		// preload of the SPI
#ifdef videoBACK_PORCH_BY_TIMER
		SPI1BUF = *l_video_address;
#endif
#ifdef videoBACK_PORCH_BY_DMA
		SPI1BUF = 0;
#endif

		// update the DMA0 source address and enable it
#ifdef videoBACK_PORCH_BY_TIMER
		DCH0SSA = KVA_TO_PA((void*)(l_video_address+1));
#endif
#ifdef videoBACK_PORCH_BY_DMA
		DCH0SSA = KVA_TO_PA((void*)(l_video_address));
#endif
		l_video_address += videoHORIZONTAL_RESOLUTION / 8;
#ifdef videoBACK_PORCH_BY_TIMER
		DmaChnEnable(0);
#endif
#ifdef videoBACK_PORCH_BY_DMA
		DmaChnEnable(1);
#endif
	}

	// clear the interrupt flag
	mT3ClearIntFlag();
	_RA3 = 0;

}
#endif